Glibc Heap Sum
2019.04.10
V1NKe
 热度
℃
大循环:
后序遍历unsort bin,从main_area
的bk指针指向chunk开始遍历,先取出,遍历一个取出一个,取出看该chunk的size,如果大小正好就是所需要的大小,那么便直接拿去使用,结束循环遍历。(后面的就不管了,保持原样
如果大小不是正好,那么便放到相应的bin中去,如果是small bin就放到small bin,是large bin就放到large bin(fast bin被包含在small bin里面了
遍历所有chunk遍历完之后,由于没有一个大小正好的chunk,那么便到刚刚所分配到不同bin中去寻找,如果size是属于small bin的,那么便到small bin中去寻找,如果有比需要的size更大的chunk存在的话,那么就切割,切割剩的一部分如果不小于0x10的话就放到unsort bin中去,如果小于就一起给了。如果small bin当中没有比需要size更大的chunk存在,那么就往large bin中去寻找了(就是在small bin中切割的情况。
在large bin里后序遍历(bk指针开始)。如果找到了比需要size大的chunk,那么也同样切割,切割剩下的一部分不小于0x10则放到unsort bin中去,小于则一起给了。
超过fast bin大小的chunk被释放后会先进入到unsort bin当中去,并且检测是否有unlink,有则进行unlink再free
small bin和unsort bin和large bin取chunk时都是取反向遍历(bk)链表。
Large Bin细节:
看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ assert (chunk_main_arena (bck->bk)); if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert (chunk_main_arena (fwd)); while ((unsigned long) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize; assert (chunk_main_arena (fwd)); } if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) /* Always insert in the second position. */ fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; if (bck->fd != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)"); } } else victim->fd_nextsize = victim->bk_nextsize = victim; }
可以看见从unsort bin卸下进入large bin的时候先是设置的fd_nextsize和bk_nextsize再去设置的fd和bk,而且新插入的size用fd_nextsize往前移位插入,值得注意的是当遇到size相同的时候只会插入到后面,并且fd_nextsize和bk_nextsize都不变即不操作,目的就是为了脱链的时候可以减少fd_nextsize和bk_nextsize的操作。用fd_nextsize往前移位插入就是可以避免多次移位遇到size相同的chunk。
large bin attack:
题目:西湖论剑-storm
往large bin里面插入chunk的时候是正向遍历(fd),直到找到size比他小的就链入他bk处。
从large bin里面取chunk的时候是反向遍历(bk),直到找到size比他大的就取出来切割。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 } else { // large bin 范围 victim_index = largebin_index(size); bck = bin_at(av, victim_index); // 当前 large bin 的头部 fwd = bck->fd; /* maintain large bins in sorted order */ /* 从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。 同样大小的 chunk,后来的只会插入到之前同样大小的 chunk 后, 而不会修改之前相同大小的fd/bk_nextsize,这也很容易理解, 可以减低开销。此外,bin 头不参与 nextsize 链接。*/ // 如果 large bin 链表不空 if (fwd != bck) { /* Or with inuse bit to speed comparisons */ // 加速比较,应该不仅仅有这个考虑,因为链表里的 chunk 都会设置该位。 size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ // bck-bk 存储着相应 large bin 中最小的chunk。 // 如果遍历的 chunk 比当前最小的还要小,那就只需要插入到链表尾部。 // 判断 bck->bk 是不是在 main arena。 assert(chunk_main_arena(bck->bk)); if ((unsigned long) (size) < (unsigned long) chunksize_nomask(bck->bk)) { // 令 fwd 指向 large bin 头 fwd = bck; // 令 bck 指向 largin bin 尾部 chunk bck = bck->bk; // victim 的 fd_nextsize 指向 largin bin 的第一个 chunk victim->fd_nextsize = fwd->fd; // victim 的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize victim->bk_nextsize = fwd->fd->bk_nextsize; // 原来链表的第一个 chunk 的 bk_nextsize 指向 victim // 原来指向链表第一个 chunk 的 fd_nextsize 指向 victim fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { // 当前要插入的 victim 的大小大于最小的 chunk // 判断 fwd 是否在 main arena assert(chunk_main_arena(fwd)); // 从链表头部开始找到不比 victim 大的 chunk while ((unsigned long) size < chunksize_nomask(fwd)) { fwd = fwd->fd_nextsize; assert(chunk_main_arena(fwd)); } // 如果找到了一个和 victim 一样大的 chunk, // 那就直接将 chunk 插入到该chunk的后面,并不修改 nextsize 指针。 if ((unsigned long) size == (unsigned long) chunksize_nomask(fwd)) /* Always insert in the second position. */ fwd = fwd->fd; else { // 如果找到的chunk和当前victim大小不一样 // 那么就需要构造 nextsize 双向链表了 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else // 如果空的话,直接简单使得 fd_nextsize 与 bk_nextsize 构成一个双向链表即可。 victim->fd_nextsize = victim->bk_nextsize = victim; } // 放到对应的 bin 中,构成 bck<-->victim<-->fwd。 mark_bin(av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
attack攻击操作:
1 2 3 4 5 6 7 8 9 10 11 12 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize;//1 fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//2 bck = fwd->bk;//3 mark_bin(av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;//4
导致有两处任意地址写操作:
1和2合并可以:fwd->bk_nextsize->fd_nextsize = victim;
3和4合并可以:fwd->bk->fd = victim;
两处写操作,看自己咋利用了,一般是拿来错位构造0x56大小的chunk。
unsort bin attack: 1 2 3 4 5 /* remove from unsorted list */ if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
控制了bk的值就能将 unsorted_chunks (av)
写到任意地址。一般拿来结合利用fast bin attack。
这里需要注意的一个点就是,house of orange
也用的是unsortbin attack
,但是他是在发生attack
之后还往后序遍历了,所以说会报错,利用报错来执行IO_file
攻击,这跟单纯的利用unosrtbin attack
还是有所区别的,所以想改global_max_fast
的时候,只需要add
一个size相等的chunk即可,因为add的size相等,发生unsortbin attack
之后该chunk就被取出来了,不会后续遍历报错了。所以上面的house of orange
就是add了一个size不等的chunk导致后续还用bk遍历了。
同样的,large bin attack
要结合mmap利用的话也是利用到了bk的后续遍历的。
Tricks:
可以利用free一个unsortbin,再add来得到libc上的地址。
也可以利用free两个fastbin,再add来得到heap上的地址。
一般题都会用的错位构造两个ptr指向同一个chunk来后续利用。
改global_max_fast
之后可以利用fastbin_index_overflow
来改libc上的任意位置为chunk的堆地址,其实就是main_area
上的fastbin_index
越界了,因为fastbin可以很大。
当libc地址上的任意一块可以被改写为fastbin的heap头地址的时候,delete掉他之后,再去改写该chunk的fd为任意东西,当再add之后,那块libc地址上的内容就变为了该chunk的fd,即可以任意写。(可以改写__free_hook
为system
,当然需要结合4一起用)
setcontext
处有一块可以改变栈空间的gadgets
(这地方在libc上)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 pwndbg> disassemble &setcontext Dump of assembler code for function setcontext: 0x00007f1d4bf64b40 <+0>: push rdi 0x00007f1d4bf64b41 <+1>: lea rsi,[rdi+0x128] 0x00007f1d4bf64b48 <+8>: xor edx,edx 0x00007f1d4bf64b4a <+10>: mov edi,0x2 0x00007f1d4bf64b4f <+15>: mov r10d,0x8 0x00007f1d4bf64b55 <+21>: mov eax,0xe 0x00007f1d4bf64b5a <+26>: syscall 0x00007f1d4bf64b5c <+28>: pop rdi 0x00007f1d4bf64b5d <+29>: cmp rax,0xfffffffffffff001 0x00007f1d4bf64b63 <+35>: jae 0x7f1d4bf64bc0 <setcontext+128> 0x00007f1d4bf64b65 <+37>: mov rcx,QWORD PTR [rdi+0xe0] 0x00007f1d4bf64b6c <+44>: fldenv [rcx] 0x00007f1d4bf64b6e <+46>: ldmxcsr DWORD PTR [rdi+0x1c0] ***0x00007f1d4bf64b75 <+53>: mov rsp,QWORD PTR [rdi+0xa0] 0x00007f1d4bf64b7c <+60>: mov rbx,QWORD PTR [rdi+0x80] 0x00007f1d4bf64b83 <+67>: mov rbp,QWORD PTR [rdi+0x78] 0x00007f1d4bf64b87 <+71>: mov r12,QWORD PTR [rdi+0x48] 0x00007f1d4bf64b8b <+75>: mov r13,QWORD PTR [rdi+0x50] 0x00007f1d4bf64b8f <+79>: mov r14,QWORD PTR [rdi+0x58] 0x00007f1d4bf64b93 <+83>: mov r15,QWORD PTR [rdi+0x60] 0x00007f1d4bf64b97 <+87>: mov rcx,QWORD PTR [rdi+0xa8] 0x00007f1d4bf64b9e <+94>: push rcx 0x00007f1d4bf64b9f <+95>: mov rsi,QWORD PTR [rdi+0x70] 0x00007f1d4bf64ba3 <+99>: mov rdx,QWORD PTR [rdi+0x88] 0x00007f1d4bf64baa <+106>: mov rcx,QWORD PTR [rdi+0x98] 0x00007f1d4bf64bb1 <+113>: mov r8,QWORD PTR [rdi+0x28] 0x00007f1d4bf64bb5 <+117>: mov r9,QWORD PTR [rdi+0x30] 0x00007f1d4bf64bb9 <+121>: mov rdi,QWORD PTR [rdi+0x68] 0x00007f1d4bf64bbd <+125>: xor eax,eax 0x00007f1d4bf64bbf <+127>: ret 0x00007f1d4bf64bc0 <+128>: mov rcx,QWORD PTR [rip+0x37c2b1] # 0x7f1d4c2e0e78 0x00007f1d4bf64bc7 <+135>: neg eax 0x00007f1d4bf64bc9 <+137>: mov DWORD PTR fs:[rcx],eax 0x00007f1d4bf64bcc <+140>: or rax,0xffffffffffffffff 0x00007f1d4bf64bd0 <+144>: ret End of assembler dump.
加星处,不过后续的push rcx;ret可以用__morecore
来平衡。
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> x/2xg 0x7f1d4c2e23b0 0x7f1d4c2e23b0 <__morecore>: 0x00007f1d4bfa48c0 0x00007f1d4bfa6140 pwndbg> disassemble 0x00007f1d4bfa48c0 Dump of assembler code for function __GI___default_morecore: 0x00007f1d4bfa48c0 <+0>: sub rsp,0x8 0x00007f1d4bfa48c4 <+4>: call 0x7f1d4c019e80 <__GI___sbrk> 0x00007f1d4bfa48c9 <+9>: mov edx,0x0 0x00007f1d4bfa48ce <+14>: cmp rax,0xffffffffffffffff 0x00007f1d4bfa48d2 <+18>: cmove rax,rdx 0x00007f1d4bfa48d6 <+22>: add rsp,0x8 0x00007f1d4bfa48da <+26>: ret End of assembler dump.
这一点一般都结合2.24及之后_IO_FILE
glibc版本使用了,因为刚好可以控制rdi
,用了这个gadgets
也可以控制rsp
了。
_dl_open_hook
跟__free_hook
差不多,只是_dl_open_hook
不为空时是执行**_dl_open_hook
的,而__free_hook
是执行*__free_hook
,而且_dl_open_hook
触发是需要malloc
或者free
出错,即double free
等等这种错误。
_IO_FILE
里面需要泄漏的时候改写的_IO_2_1_stdout
的flag
需要满足的条件是:
1 if f->flag & 0xa00 and f->flag & 0x1000 == 1 then it will leak something when f->write_base != f->write_ptr
而且泄漏范围是从write_base
到write_ptr
。(flag并不非要0xfbad
起始)
_IO_FILE
其实2.23之前随便折腾,2.24开始(包括)都不能改变vtable
表,反而需要把他指向_IO_str_jmps
,利用调用其中的函数来构造并且触发我们想要的函数。当然前提也需要把_IO_list_all
给改了,FSOP
是劫持了_IO_list_all
并且是把vtable
也给改了。